package com.pilgrim.framework.common.util;

import com.alibaba.fastjson.JSON;
import com.google.common.base.Strings;
import lombok.extern.slf4j.Slf4j;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.*;

/**
 * 接口请求参数签名工具
 *
 * @author wangyibin
 */
@Slf4j
public class SignatureUtil {

    /**
     * 加密算法
     */
    private static final String KEY_ALGORITHM = "AES";
    public static final String CHARSET_UTF8 = "UTF-8";

    /**
     * CBC默认的加密算法
     */
    private static final String DEFAULT_CIPHER_ALGORITHM_CBC = "AES/CBC/PKCS5Padding";

    /**
     * 获取包含大小写字符数字和特殊字符的随机密码
     *
     * @param len 密码长度
     * @return 密码
     */
    public static String getRandomPassword(int len) {
        String result = null;
        len = len >= 6 ? len : 6;
        while (true) {
            result = makeRandomPassword(len);
            if (result.matches(".*[a-z]{1,}.*") && result.matches(".*[A-Z]{1,}.*") && result.matches(".*\\d{1,}.*")
                && result.matches(".*[~!@#$%^&*\\.?]{1,}.*")) {
                return result;
            }
        }
    }

    /**
     * 随机密码生成
     *
     * @param len 密码位数
     * @return 密码
     */
    public static String makeRandomPassword(int len) {
        char[] charArr= "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890~!@#$%^&*.?".toCharArray();
        StringBuilder sb = new StringBuilder();
        Random r = new Random();
        for (int x = 0; x < len; ++x) {
            sb.append(charArr[r.nextInt(charArr.length)]);
        }
        return sb.toString();
    }

    /***
     * BASE64解密
     * @param key 加密字符
     * @return 解密结果
     */
    public static byte[] decryBase64(String key) {
        return Base64.getDecoder().decode(key);
    }

    /***
     * BASE64加密
     * @param key 原始字节
     * @return 加密字符
     */
    public static String encryptBase64(byte[] key) {
        return Base64.getEncoder().encodeToString(key);
    }

    /***
     * 加密
     * @param encodeRules 加密规则
     * @param content 需要加密的参数
     * @return 密文
     */
    public static String encryptByAes(String encodeRules,String content) {
        try {
            //1.构造密钥生成器，指定为AES算法,不区分大小写
            KeyGenerator keygen = KeyGenerator.getInstance(KEY_ALGORITHM);
            //2.根据ecnodeRules规则初始化密钥生成器
            //生成一个128位的随机源,根据传入的字节数组
            SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG" );
            secureRandom.setSeed(encodeRules.getBytes(CHARSET_UTF8));
            keygen.init(128, secureRandom);
            //3.产生原始对称密钥
            SecretKey originalKey = keygen.generateKey();
            //4.获得原始对称密钥的字节数组
            byte[] raw = originalKey.getEncoded();
            //5.根据字节数组生成AES密钥
            SecretKey key = new SecretKeySpec(raw, KEY_ALGORITHM);
            //6.根据指定算法AES自成密码器
            Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
            //7.初始化密码器，第一个参数为加密(Encrypt_mode)或者解密解密(Decrypt_mode)操作，第二个参数为使用的KEY
            cipher.init(Cipher.ENCRYPT_MODE, key);
            //8.获取加密内容的字节数组(这里要设置为utf-8)不然内容中如果有中文和英文混合中文就会解密为乱码
            byte[] byteEncode = content.getBytes(CHARSET_UTF8);
            //9.根据密码器的初始化方式--加密：将数据加密
            byte[] byteAes = cipher.doFinal(byteEncode);
            //10.将加密后的数据转换为字符串
            return encryptBase64(byteAes);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 解密
     *
     * @param encodeRules 需要解密的规则
     * @param content 需要解密的参数
     * @return 明文
     */
    public static String decryptByAes(String encodeRules, String content) {
        try {
            //1.构造密钥生成器，指定为AES算法,不区分大小写
            KeyGenerator keygen = KeyGenerator.getInstance(KEY_ALGORITHM);
            //2.根据ecnodeRules规则初始化密钥生成器
            //生成一个128位的随机源,根据传入的字节数组
            SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG" );
            secureRandom.setSeed(encodeRules.getBytes(CHARSET_UTF8));
            keygen.init(128, secureRandom);
            //3.产生原始对称密钥
            SecretKey originalKey = keygen.generateKey();
            //4.获得原始对称密钥的字节数组
            byte[] raw = originalKey.getEncoded();
            //5.根据字节数组生成AES密钥
            SecretKey key = new SecretKeySpec(raw, KEY_ALGORITHM);
            //6.根据指定算法AES自成密码器
            Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
            //7.初始化密码器，第一个参数为加密(Encrypt_mode)或者解密(Decrypt_mode)操作，第二个参数为使用的KEY
            cipher.init(Cipher.DECRYPT_MODE, key);
            //8.将加密并编码后的内容解码成字节数组
            byte[] byteContent = decryBase64(content);
            // 解密
            byte[] byteDecode = cipher.doFinal(byteContent);
            String aesDecode = new String(byteDecode, CHARSET_UTF8);
            return aesDecode;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * CBC AES加密操作
     *
     * @param content content
     * @param key key
     * @param ivParameter ivParameter
     * @return String
     */
    public static String encryptCbc(String content, String key, String ivParameter) {
        try {
            // 创建密码器
            Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM_CBC);
            byte[] byteContent = content.getBytes(CHARSET_UTF8);
            IvParameterSpec ivParameterSpec = new IvParameterSpec(ivParameter.getBytes(CHARSET_UTF8));
            // 初始化为加密模式的密码器
            cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key.getBytes(CHARSET_UTF8), KEY_ALGORITHM), ivParameterSpec);
            // 加密
            byte[] result = cipher.doFinal(byteContent);
            //通过Base64转码返回
            return org.apache.commons.codec.binary.Base64.encodeBase64String(result);
        } catch (Exception ex) {
            log.error("AES encrypt error ", ex);
        }
        return null;
    }

    /**
     * CBC AES 解密操作
     *
     * @param content content
     * @param key key
     * @param ivParameter ivParameter
     * @return String
     */
    public static String decryptCbc(String content, String key, String ivParameter) {

        try {
            //实例化
            Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM_CBC);
            IvParameterSpec ivParameterSpec = new IvParameterSpec(ivParameter.getBytes(CHARSET_UTF8));
            //使用密钥初始化，设置为解密模式
            cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key.getBytes(CHARSET_UTF8), KEY_ALGORITHM), ivParameterSpec);
            //执行操作
            byte[] result = cipher.doFinal(org.apache.commons.codec.binary.Base64.decodeBase64(content));
            return new String(result, CHARSET_UTF8);
        } catch (Exception ex) {
            log.error("AES decrypt error ", ex);
        }
        return null;
    }

    /**
     * 校验sign
     *
     * @param jsonString jsonString
     * @param apiSecret apiSecret
     * @return boolean
     */
    public static boolean verifySignature(String jsonString, String apiSecret) {
        try {
            Map paramMap = JSON.parseObject(jsonString);
            paramMap.put("apiSecret", apiSecret);
            String requestSign = (String) paramMap.get("sign");
            if (Strings.isNullOrEmpty(requestSign)) {
                log.warn("parse request sign must not blank");
                return false;
            }
            paramMap.remove("sign");
            String parameterString = sortParameter(paramMap);
            String expectedSign = Md5Util.encode(parameterString);
            return Objects.equals(requestSign, expectedSign);
        } catch (Exception e) {
            log.error("parse request body json to map fail", e);
            return false;
        }
    }


    /**
     * 创建带sign的请求json字符串
     *
     * @param jsonString jsonString
     * @param apiSecret apiSecret
     * @return String
     */
    public static String generateSignature(String jsonString, String apiSecret) {
        try {
            Map paramMap = JSON.parseObject(jsonString);
            paramMap.put("apiSecret", apiSecret);
            paramMap.remove("sign");
            String parameterString = sortParameter(paramMap);
            String sign = Md5Util.encode(parameterString);
            paramMap.put("sign", sign);
            return JSON.toJSONString(paramMap);
        } catch (Exception e) {
            log.error("parse request body json to map fail", e);
            return "";
        }
    }


    /**
     * 请求参数按key正序排序，并拼接返回字符串
     *
     * @param info info
     * @return String
     */
    private static String sortParameter(Map<String, Object> info) {

        List<Map.Entry<String, Object>> infoIds = new ArrayList<>(info.entrySet());
        Collections.sort(infoIds, Comparator.comparing(Map.Entry<String, Object>::getKey));
        String resultString;
        StringBuilder stringBuilder = new StringBuilder();
        infoIds.forEach(entry -> {
            Object value = entry.getValue();
            stringBuilder
                .append(String.format("%s=%s&", entry.getKey(), Objects.nonNull(value) ? value.toString() : ""));
        });
        resultString = stringBuilder.substring(0, stringBuilder.length() - 1);
        return resultString;
    }

    public static void main(String[] args) {
        String s = encryptByAes("1ee6vk91g", "123456");
        System.out.println(s);
        String pw = decryptByAes("1ee6vk91g", s);
        System.out.println(pw);
    }
}
